﻿using System;
using System.Data;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using MIAP.Negocio.Properties;
using NPOI.HSSF.UserModel;
using NPOI.SS.UserModel;

namespace MIAP.Negocio.Utiles
{
    public class ExcelHandling
    {
        /// <summary>
        /// libro de excel
        /// </summary>
        private IWorkbook _workbook;

        /// <summary>
        /// nombre del excel a manejar
        /// </summary>
        private string _nombreExcel;

        /// <summary>
        /// Xml con el cual validaremos el excel
        /// </summary>
        private LecturaXML _lecturaXml;

        /// <summary>
        /// builder de elementos del excel con error
        /// </summary>
        private StringBuilder _errores;

        /// <summary>
        /// regresamos los errores encontrados en el excel
        /// </summary>
        public string Errores
        {
            get { return _errores.ToString(); }
        }

        /// <summary>
        /// inicializa el libro con el stream dado y cierra el stream que fue pasado como parametro
        /// </summary>
        /// <param name="streamExcel">contenido del excel</param>
        /// <param name="nombreExcel">nombre del archivo excel</param>
        /// <param name="stringXml">XML con el que se validara el excel</param>
        public ExcelHandling(Stream streamExcel, string nombreExcel, string stringXml)
        {
            this._workbook = WorkbookFactory.Create(streamExcel);
            streamExcel.Close();
            this._nombreExcel = nombreExcel;
            this._lecturaXml = new LecturaXML(stringXml);
            this._errores = new StringBuilder();
        }

        /// <summary>
        /// Valida el elcel dependiendo de las reglas contenidas en el archivo XML
        /// </summary>
        /// <returns>string vacio si no existe errores en el excel, de lo contrario regresara una descripción con los errores encontrados</returns>
        public string ValidaExcel()
        {
            var sErrores = new StringBuilder();
            if (!this.ValidaFormatoNombreExcel())
            {
                sErrores.AppendLine(String.Format(Strings.ErrorFormatoNombre, this._lecturaXml.ObtenValorAtributoExcel(LecturaXML.FormatoNombre)));
            }
            if (!this.ValidaNombreHoja())
            {
                sErrores.AppendLine(String.Format(Strings.ErrorExistenciaHoja, this._lecturaXml.ObtenValorAtributoExcel(LecturaXML.NombreHoja)));
                return sErrores.ToString();
            }
            sErrores.Append(this.ValidaColumnasExcel());

            return sErrores.ToString();
        }

        /// <summary>
        /// Valida que el nombre de hoja dado exista en el libro de excel.
        /// </summary>
        /// <returns></returns>
        private bool ValidaNombreHoja()
        {
            var nombreHoja = this._lecturaXml.ObtenValorAtributoExcel(LecturaXML.NombreHoja);
            foreach (ISheet sheet in _workbook)
            {
                if (sheet.SheetName.Equals(nombreHoja))
                {
                    return true;
                }
            }
            return false;
        }

        /// <summary>
        /// Valida que el nombre del excel sea como el que se encuentra especificado en e xml
        /// </summary>
        /// <returns></returns>
        private bool ValidaFormatoNombreExcel()
        {
            var formatoNombre = this._lecturaXml.ObtenValorAtributoExcel(LecturaXML.FormatoNombre);
            DateTime tfecha;
            var fecha = string.Empty;
            var formatoFecha = string.Empty;
            if (string.IsNullOrEmpty(formatoNombre))
            {
                return true;
            }
            if (formatoNombre.Contains("_YYYYMMDD") || formatoNombre.Contains("_YYYYMM") || formatoNombre.Contains("_YYYY")
                || formatoNombre.Contains("_YY"))
            {
                formatoFecha = formatoNombre.Split('_')[formatoNombre.Split('_').Length - 1].ToLower();
                formatoFecha = formatoFecha.Replace('m', 'M');
                var split = _nombreExcel.Split('_');
                if (split.Length > 2)
                    return false;
                fecha = split[split.Length - 1].Split('.')[0];
            }
                //si no tiene el formato entonces se debe de llamar exactamente igual
            else if (!formatoNombre.Contains("_YYYYMMDD") && !formatoNombre.Contains("_YYYYMM") &&
                     !formatoNombre.Contains("_YYYY")
                     && !formatoNombre.Contains("_YY"))
            {
                return formatoNombre.Trim().ToLower() == _nombreExcel.Split('.')[0].Trim().ToLower();
            }
            return DateTime.TryParseExact(fecha, formatoFecha, CultureInfo.InvariantCulture, DateTimeStyles.AllowWhiteSpaces, out tfecha);
        }


        /// <summary>
        /// Valida que el nombre del excel sea como el que se encuentra especificado en el xml
        /// </summary>
        /// <returns></returns>
        private string ValidaColumnasExcel()
        {
            var sErrores = new StringBuilder();
            var listaColumnas = this._lecturaXml.ObtenerValorColumnasExcelXML();
            var hojaExcel = _workbook.GetSheet(this._lecturaXml.ObtenValorAtributoExcel(LecturaXML.NombreHoja));
            var row0 = hojaExcel.GetRow(hojaExcel.FirstRowNum);
            if (listaColumnas.Count != row0.Cells.Count)
            {
                sErrores.Append(Strings.ErrorNoColumnas);
            }
            for (var icol = 0; icol < listaColumnas.Count; icol++)
            {
                var cell = row0.GetCell(icol);
                var nombre = cell.StringCellValue;
                var colExcel = this.GetLetterRow(icol + 1);
                if (nombre != listaColumnas[icol].NombreColumna)
                {
                    sErrores.AppendLine(String.Format(Strings.ErrorNombreColumna, nombre,
                                                      listaColumnas[icol].NombreColumna));
                }
                if (colExcel != listaColumnas[icol].IndiceColumna)
                {
                    sErrores.AppendLine(String.Format(Strings.ErrorColumnaExcel, colExcel,
                                                      listaColumnas[icol].IndiceColumna));
                }
            }
            return sErrores.ToString();
        }

        /// <summary>
        /// Obtiene la letra de la columna de excel dependiendo de la posicion en la que esta
        /// </summary>
        /// <param name="noColumna"></param>
        /// <returns></returns>
        private string GetLetterRow(int noColumna)
        {
            var name = "";
            while (noColumna > 0)
            {
                noColumna--;
                name = (char)('A' + noColumna % 26) + name;
                noColumna /= 26;
            }
            return name;
        }

        /// <summary>
        /// Valida el excel y si es correcto regresa un DataSet con la informacion de este si no regresa null y
        /// los errores los coloca en la variable errores
        /// </summary>
        /// <returns></returns>
        public DataSet ValidayConvierteToDataSet()
        {
            var loErrores = this.ValidaExcel();
            if (String.IsNullOrEmpty(loErrores))
            {
                return this.ConvertToDataSet();
            }
            this._errores.Append(loErrores);
            return null;
        }

        /// <summary>
        /// Regresa el contenido del excel el un dataset
        /// </summary>
        /// <returns></returns>
        public DataSet ConvertToDataSet()
        {
            var nombreHoja = this._lecturaXml.ObtenValorAtributoExcel(LecturaXML.NombreHoja);
            var sheet = this._workbook.GetSheet(nombreHoja);
            var dataSet1 = new DataSet(nombreHoja);
            var columnas = this._lecturaXml.ObtenerValorColumnasExcelXML();
            var dt = new DataTable();
            var errores = false;
            foreach (ICell t in sheet.GetRow(sheet.FirstRowNum))
            {
                dt.Columns.Add(t.StringCellValue);
            }

            if (sheet.FirstRowNum == sheet.LastRowNum)
            {
                this._errores.AppendLine(Strings.ExcelVacio);
                return  null;
            }
            for (var j = sheet.FirstRowNum + 1; j <= sheet.LastRowNum; j++)
            {
                var row = sheet.GetRow(j);
                if (row == null) continue;

                var dr = dt.NewRow();
                if (sheet.GetRow(sheet.FirstRowNum).Cells.Count > row.Cells.Count()) continue;
                for (var i = 0; i < sheet.GetRow(sheet.FirstRowNum).Cells.Count; i++)
                {
                    var cell = row.GetCell(i);
                    var letterCell = this.GetLetterRow(i + 1);
                    var columna = columnas.Single(c => c.IndiceColumna == letterCell);

                    if (!this.CompareCellType(cell, columna.TypeVariable))
                    {
                        this._errores.AppendLine(String.Format(Strings.ErrorTipoExcel, letterCell, row.RowNum+1,
                                                               columna.TipoVariable));
                        errores = true;
                    }

                    dr[i] = cell == null ? null : cell.ToString();
                }
                dt.Rows.Add(dr);
            }

            dataSet1.Tables.Add(dt);
            return errores ? null : dataSet1;
        }

        /// <summary>
        /// Comparamos el tipo de celdas y vemos si es el correcto
        /// </summary>
        /// <param name="celda"></param>
        /// <param name="tipoDefinido"></param>
        /// <returns></returns>
        private bool CompareCellType(ICell celda, Type tipoDefinido)
        {
            var tipo = typeof(int);
            switch (celda.CellType)
            {
                case CellType.BLANK:
                    tipo = typeof(string);
                    break;
                case CellType.BOOLEAN:
                    tipo = typeof(bool);
                    break;
                case CellType.ERROR:
                    tipo = typeof(string);
                    break;
                case CellType.FORMULA:
                    // Get the type of Formula 
                    switch (celda.CachedFormulaResultType)
                    {
                        case CellType.NUMERIC:
                            tipo = typeof(double);
                            break;
                        case CellType.STRING:
                            tipo = typeof(string);
                            break;
                    }
                    break;
                case CellType.NUMERIC:
                    tipo = typeof(double);
                    break;
                default:
                    tipo = typeof(string);
                    break;

            }
            return tipo == tipoDefinido;

        }
    }
}
